package me.aip.mov.Interceptor;


import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import me.aip.mov.annotation.AccessLimit;
import me.aip.mov.annotation.GlobalResult;
import me.aip.mov.annotation.PassToken;
import me.aip.mov.constant.Constant;
import me.aip.mov.entity.UserInfo;
import me.aip.mov.exception.ServiceException;
import me.aip.mov.response.ResponseResult;
import me.aip.mov.response.ResultCode;
import me.aip.mov.service.IUserInfoService;
import me.aip.mov.util.IpUtil;
import me.aip.mov.util.JwtUtil;
import me.aip.mov.util.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author aip9105
 * 说明：1.统一格式返回 类或方法使用@GlobalResponseResult注解
 * 2.防刷限流  方法使用@AccessLimit(maxCount=1,second=5)注解 5秒内访问一次
 */
@Slf4j
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    /**
     * 标记名称
     */
    private final static String GLOBAL_RESPONSE_RESULT = "GLOBAL_RESPONSE_RESULT";
    private final static String TOKEN = "Authorization";
    private final static String TOKEN_WITH_BEARER = "Bearer ";

    @Autowired
    IUserInfoService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        final Class<?> clazz = handlerMethod.getBeanType();
        final Method method = handlerMethod.getMethod();
        //统一返回校验
        if (handler instanceof HandlerMethod) {
            //判断是否在类ResponseResult对象上加了注解,统一返回
            if (clazz.isAnnotationPresent(GlobalResult.class)) {
                //设置请求体需要包装,往下传递,在ResponseBodyAdvice接口判断传递
                request.setAttribute(GLOBAL_RESPONSE_RESULT, clazz.getAnnotation(GlobalResult.class));
            } else if (method.isAnnotationPresent(GlobalResult.class)) {//判断方法方法是否加了注解
                //设置请求体需要包装,往下传递,在ResponseBodyAdvice接口判断传递
                request.setAttribute(GLOBAL_RESPONSE_RESULT, method.getAnnotation(GlobalResult.class));
            }
        }
        if ("/error".equals(request.getRequestURI())){
            return true;
        }
        //接口防刷限流拦截器
        AccessLimit annotation = method.getAnnotation(AccessLimit.class);
        if (annotation != null) {
            check(annotation, request);
        }
        //判断是否在类ResponseResult对象上加了注解,统一返回
        if (clazz.isAnnotationPresent(PassToken.class)) {
            return true;
        }
        //检查是否有passtoken注释，有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        String token = request.getHeader(TOKEN);
        if (Objects.isNull(token)) {
            String result = JSON.toJSONString(ResponseResult.failure(ResultCode.TOKEN_LOSED));
            returnJson(response, result);
            return false;
        }
        //去除Bearer
        token = token.replace(TOKEN_WITH_BEARER, "");
        //解析token获取用户id
        String userId = JwtUtil.getUserId(token);
        //根据用户名获取用户实体，在实际开发中从redis取
        UserInfo user = userService.getById(userId);
        if (Objects.isNull(user)) {
            String result = JSON.toJSONString(ResponseResult.failure(ResultCode.TOKEN_FAIL));
            returnJson(response, result);
            return false;
        }
        //校验token是否失效，自动续期
        if (!refreshToken(token, user.getUserId(), user.getPassword())) {
            String result = JSON.toJSONString(ResponseResult.failure(ResultCode.TOKEN_EXPIRED));
            returnJson(response, result);
            return false;
        }
        return true;
    }

    private void check(AccessLimit annotation, HttpServletRequest request) throws ServiceException {
        int maxCount = annotation.maxCount();
        int seconds = annotation.seconds();

        StringBuilder sb = new StringBuilder();
        sb.append(Constant.Redis.ACCESS_LIMIT_PREFIX).append(IpUtil.getIpAddress(request)).append(request.getRequestURI());
        String key = sb.toString();
        Boolean exists = RedisUtil.KeyOps.hasKey(key);
        if (!exists) {
            RedisUtil.StringOps.setEx(key, String.valueOf(1), seconds, TimeUnit.SECONDS);
        } else {
            int count = Integer.valueOf(RedisUtil.StringOps.get(key));
            if (count < maxCount) {
                Long ttl = RedisUtil.KeyOps.getExpire(key);
                if (ttl <= 0) {
                    RedisUtil.StringOps.setEx(key, String.valueOf(1), seconds, TimeUnit.SECONDS);
                } else {
                    RedisUtil.StringOps.setEx(key, String.valueOf(++count), ttl.intValue(), TimeUnit.SECONDS);
                }
            } else {
                throw new ServiceException(ResultCode.ACCESS_LIMIT.getMessage());
            }
        }
    }

    /**
     * 返回客户端数据
     */
    private void returnJson(HttpServletResponse response, String result) {
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(result);

        } catch (IOException e) {
        } finally {
            if (writer != null) {
                writer.close();
            }
        }

    }

    public boolean refreshToken(String token,String userId, String passWord) {
        String tokenKey = Constant.Redis.TOKEN_PREFIX + token;
        String cacheToken = RedisUtil.StringOps.get(tokenKey);
        if (StringUtils.isNotEmpty(cacheToken)) {
            // 校验token有效性，注意需要校验的是缓存中的token
            if (!JwtUtil.verify(cacheToken, userId, passWord)) {
                String newToken = JwtUtil.sign(userId, passWord);
                // 设置超时时间 将redis的过期时间设置成token过期时间的两倍
                RedisUtil.StringOps.setEx(tokenKey, newToken, 30 * 60 * 2, TimeUnit.SECONDS);

            }
            return true;
        }
        return false;
    }

}
